/*
* Copyright 2013 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.jenkins.plugins.dsl.util;
import java.util.List;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.MockitoAnnotations;
import com.google.common.collect.Lists;
import com.google.jenkins.plugins.dsl.restrict.AbstractRestriction;
import com.google.jenkins.plugins.dsl.restrict.BadTypeException;
import com.google.jenkins.plugins.dsl.restrict.NoRestriction;
import com.google.jenkins.plugins.dsl.restrict.RestrictedProject;
import hudson.matrix.MatrixProject;
import hudson.matrix.TextAxis;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.model.Job;
import hudson.model.ListView;
import hudson.model.View;
import hudson.model.ViewGroupMixIn;
import hudson.plugins.git.GitSCM;
import hudson.scm.NullSCM;
import hudson.scm.SCM;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Maven;
import hudson.tasks.Shell;
import hudson.views.JobColumn;
import hudson.views.StatusColumn;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
/**
* Tests for {@link Binder$Default}.
*/
public class BinderTest {
@Rule
public JenkinsRule jenkins = new JenkinsRule();
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private Binder.Default underTest;
private FreeStyleProject project;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
project = jenkins.createFreeStyleProject("test");
final AbstractRestriction restriction = new NoRestriction();
underTest = new Binder.Default(restriction.getClassLoader(
new RestrictedProject<AbstractProject>() {
@Override
public AbstractProject asProject() {
return project;
}
@Override
public AbstractRestriction getRestriction() {
return restriction;
}
}));
views = Lists.newArrayList();
viewGroupMixIn = new ViewGroupMixIn(Jenkins.getInstance()) {
/** {@inheritDoc} */
@Override
protected List<View> views() {
return views;
}
/** {@inheritDoc} */
@Override
protected String primaryView() {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override
protected void primaryView(String name) {
throw new UnsupportedOperationException();
}
};
}
@Test
public void testGetDescriptor_WellFormed() throws Exception {
Descriptor descriptor = underTest.getDescriptor(Shell.class.getName());
assertNotNull(descriptor);
}
@Test
public void testGetDescriptor_Typo() throws Exception {
final String badClass = "hudson.tasks.Shellz";
try {
underTest.getDescriptor(badClass);
fail("expected exception");
} catch (BadTypeException e) {
assertThat(e.getMessage(), containsString(badClass));
}
}
@Test
public void testGetDescriptor_NotDescribable() throws Exception {
final String goodClass = "java.util.Map";
try {
underTest.getDescriptor(goodClass);
fail("expected exception");
} catch (BadTypeException e) {
assertThat(e.getMessage(), containsString(goodClass));
}
}
@Test
public void testBind_DataBoundConstructor() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(SHELL_JSON);
Shell shell = underTest.bind(json);
assertNotNull(shell);
// Test that the data came through
assertEquals("echo Hello World", shell.getCommand());
}
@Test
public void testBind_NoDataBoundConstructor() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(NULLSCM_JSON);
NullSCM scm = underTest.bind(json);
assertNotNull(scm);
}
@Test
public void testBind_Extension() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(GITSCM_JSON);
GitSCM scm = underTest.bind(json);
assertNotNull(scm);
}
@Test
public void testBind_BadCast() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(SHELL_JSON);
try {
// Return type is important for cast!
SCM scm = underTest.bind(json);
fail("expected exception");
} catch (ClassCastException e) {
assertThat(e.getMessage(),
allOf(containsString(json.optString("$class", null)),
containsString(SCM.class.getName())));
}
}
@Test
public void testBindJob_EmptyNoClass() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_JSON);
Job job = underTest.bindJob(Jenkins.getInstance(), NAME, json);
assertNotNull(job);
assertThat(job, instanceOf(FreeStyleProject.class));
assertEquals(NAME, job.getName());
}
@Test
public void testBindJob_EmptyWithClass() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_JSON);
json.put("$class", FreeStyleProject.class.getName());
Job job = underTest.bindJob(Jenkins.getInstance(), NAME, json);
assertNotNull(job);
assertThat(job, instanceOf(FreeStyleProject.class));
assertEquals(NAME, job.getName());
}
@Test
public void testBindJob_EmptyWithMatrixClassNoAxes() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_JSON);
json.put("$class", MatrixProject.class.getName());
Job job = underTest.bindJob(Jenkins.getInstance(), NAME, json);
assertNotNull(job);
assertThat(job, instanceOf(MatrixProject.class));
assertEquals(NAME, job.getName());
}
@Test
public void testBindJob_EmptyWithMatrixClassAndAxes() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_JSON);
json.put("$class", MatrixProject.class.getName());
JSONArray axes = (JSONArray) JSONSerializer.toJSON(AXES_JSON);
json.put("axis", axes);
Job job = underTest.bindJob(Jenkins.getInstance(), NAME, json);
assertNotNull(job);
assertThat(job, instanceOf(MatrixProject.class));
assertEquals(NAME, job.getName());
assertEquals(2, ((MatrixProject) job).getAxes().size());
}
@Test
public void testBindJob_WithBuilder() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_JSON);
JSONArray builders = (JSONArray) JSONSerializer.toJSON(BUILDERS1_JSON);
json.put("builder", builders);
Job job = underTest.bindJob(Jenkins.getInstance(), NAME, json);
assertNotNull(job);
assertThat(job, instanceOf(FreeStyleProject.class));
assertEquals(NAME, job.getName());
FreeStyleProject project = (FreeStyleProject) job;
assertEquals(1, project.getBuilders().size());
assertThat(project.getBuilders().get(0), instanceOf(Shell.class));
}
@Test
public void testBindJob_FullJob() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_JSON);
// TODO(mattmoor): The logic in hudson.scm.SCMS uses the index of the SCM
// among the collection of compatible SCMS for the given project to
// determine which class to bind instead of relying on the '$class' field.
// I have prototyped a fix for this, but until it can be pushed back,
// we cannot test this.
// JSONObject scm = (JSONObject) JSONSerializer.toJSON(GITSCM_JSON);
// json.put("scm", scm);
JSONArray builders = (JSONArray) JSONSerializer.toJSON(BUILDERS2_JSON);
json.put("builder", builders);
JSONArray publishers = (JSONArray) JSONSerializer.toJSON(PUBLISHERS_JSON);
json.put("publisher", publishers);
Job job = underTest.bindJob(Jenkins.getInstance(), NAME, json);
assertNotNull(job);
assertThat(job, instanceOf(FreeStyleProject.class));
assertEquals(NAME, job.getName());
FreeStyleProject project = (FreeStyleProject) job;
assertEquals(1, project.getBuilders().size());
assertThat(project.getBuilders().get(0), instanceOf(Maven.class));
// NOTE: getPublishers returns a Map, but the above returns a list
assertEquals(1, project.getPublishers().values().size());
assertThat(project.getPublishers().values().toArray()[0],
instanceOf(BuildTrigger.class));
}
private List<View> views;
private ViewGroupMixIn viewGroupMixIn;
@Test
public void testBindView_simple() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_LIST_VIEW);
json.put("name", NAME);
View view = underTest.bindView(viewGroupMixIn, NAME, json);
assertNotNull(view);
assertThat(view, instanceOf(ListView.class));
assertEquals(NAME, view.getViewName());
assertTrue(views.contains(view));
}
@Test
public void testBindView_simpleWithJob() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_LIST_VIEW);
json.put("name", NAME);
// Add our job to the list view
json.put(project.getName(), true);
View view = underTest.bindView(viewGroupMixIn, NAME, json);
assertNotNull(view);
assertThat(view, instanceOf(ListView.class));
assertEquals(NAME, view.getViewName());
assertTrue(views.contains(view));
ListView listView = (ListView) view;
assertTrue(listView.jobNamesContains(project));
}
@Test
public void testBindView_simpleWithJobAndColumns() throws Exception {
JSONObject json = (JSONObject) JSONSerializer.toJSON(EMPTY_LIST_VIEW);
json.put("name", NAME);
// Add our job to the list view
json.put(project.getName(), true);
// Add some columns to the view.
JSONArray columns = (JSONArray) JSONSerializer.toJSON(COLUMNS_JSON);
json.put("columns", columns);
View view = underTest.bindView(viewGroupMixIn, NAME, json);
assertNotNull(view);
assertThat(view, instanceOf(ListView.class));
assertEquals(NAME, view.getViewName());
assertTrue(views.contains(view));
ListView listView = (ListView) view;
assertTrue(listView.jobNamesContains(project));
assertEquals(2, listView.getColumns().size());
}
// TODO(mattmoor): Test build wrappers
private static final String NAME = "bazinga";
private static final String SHELL_JSON =
"{ '$class': '" + Shell.class.getName() + "', " +
" 'command': 'echo Hello World' }";
private static final String NULLSCM_JSON =
"{ '$class': '" + NullSCM.class.getName() + "' }";
private static final String EMPTY_LIST_VIEW =
"{ '$class': '" + ListView.class.getName() + "' }";
// TODO(mattmoor): Eliminate this once properties is properly null checked
private static final String EMPTY_JSON =
"{ 'properties': {} }";
private static final String AXIS1_JSON =
"{ '$class': '" + TextAxis.class.getName() + "', " +
"'name': 'a', 'valueString': '1 2' }";
private static final String AXIS2_JSON =
"{ '$class': '" + TextAxis.class.getName() + "', " +
"'name': 'b', 'valueString': '3' }";
private static final String AXES_JSON =
"[ " + AXIS1_JSON + ", " + AXIS2_JSON + " ]";
private static final String BUILDERS1_JSON =
"[ " + SHELL_JSON + " ]";
private static final String GITSCM_JSON =
"{ " +
" '$class': '" + GitSCM.class.getName() + "', " +
" 'userRemoteConfigs': {" +
" 'url': 'https://github.com/jenkinsci/my-plugin.git' " +
" }, " +
" 'branches': {" +
" 'name': 'master' " +
" }, " +
"}";
private static final String MAVEN_JSON =
"{ " +
" '$class': '" + Maven.class.getName() + "', " +
" 'targets': 'clean package', " +
"}";
private static final String BUILDERS2_JSON =
"[ " + MAVEN_JSON + " ]";
private static final String JOB_COLUMN_JSON =
"{ " +
" '$class': '" + JobColumn.class.getName() + "', " +
"}";
private static final String STATUS_COLUMN_JSON =
"{ " +
" '$class': '" + StatusColumn.class.getName() + "', " +
"}";
private static final String COLUMNS_JSON =
"[ " + STATUS_COLUMN_JSON + ", " + JOB_COLUMN_JSON + " ]";
private static final String TRIGGER_JSON =
"{ " +
" '$class': '" + BuildTrigger.class.getName() + "', " +
" 'childProjects': 'test', " +
" 'threshold': 'SUCCESS', " +
"}";
private static final String PUBLISHERS_JSON =
"[ " + TRIGGER_JSON + " ]";
}